﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Noris.Schedule.Support.Services
{
	// DEKLARACE interface a args PRO SLUŽBY TYPU "GENERÁTOR VIZUÁLNÁCH "
	#region INTERFACE IGraphElementPalette : předpis pro Generátor palety elementů
	/// <summary>
	/// Interface, který předepisuje vlastnosti objektu, který chce být generátorem palety elementů grafu.
	/// </summary>
	public interface IGraphElementPalette : IPlugin
	{
		/// <summary>
		/// Zde generátor palety vyjadřuje svoji touhu pracovat pro konkrétní typ grafu.
		/// Na vstupu je uvedena specifikace grafu: typ grafu, číslo třídy main řádku.
		/// Zdejší objekt vyhodnotí zadané údaje a rozhodne se, zda bude chtít daný pro typ grafu generovat položky palety.
		/// Pokud nechce malovat, vrátí 0.
		/// Pokud se sejde více tříd generátorů palety, které by chtěly pracovat pro stejný graf, pak vyhraje ta, která vrací vyšší číslo.
		/// Tato metoda se volá nejvýše jedenkrát za života jednoho grafu, vrácená hodnota platí pro tento graf až do konce jeho života.
		/// </summary>
		/// <param name="graphKey">Specifikace grafu</param>
		/// <returns>Hodnota vyjadřující moji touhu pracovat pro tento graf</returns>
		float GetPriority(GraphElementPaletteKey graphKey);
		/// <summary>
		/// Generátor palety vytvoří a vrátí pero pro daný element a danou část elementu.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		Pen GetPen(GraphElementPaletteArgs args);
		/// <summary>
		/// Generátor palety vytvoří a vrátí štětec pro daný element a danou část elementu.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		Brush GetBrush(GraphElementPaletteArgs args, Rectangle rectangle, out GraphicsPaletteBrushRepeatType repeatType);
		/// <summary>
		/// Generátor palety vytvoří a vrátí font pro daný element a danou část elementu.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		Font GetFont(GraphElementPaletteArgs args);
		/// <summary>
		/// Generátor palety vytvoří a vrátí obrázek pro daný element a danou část elementu.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		Image GetImage(GraphElementPaletteArgs args);
	}
	#endregion
	#region CLASS GraphElementPaletteKey : SPECIFIKACE GRAFU, KTERÝ MÁ BÝT KRESLEN
	/// <summary>
	/// Specifikace grafu, který se má kreslit.
	/// </summary>
	public class GraphElementPaletteKey
	{
		public GraphElementPaletteKey(RowGraphMode graphMode, int classNumber)
		{
			this._GraphMode = graphMode;
			this._ClassNumber = classNumber;
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "{GraphMode=" + GraphMode + "; ClassNumber=" + ClassNumber + "}";
		}
		public override int GetHashCode()
		{
			return this._GraphModeInt ^ this._ClassNumber;
		}
		public override bool Equals(object obj)
		{
			if (obj is GraphElementPaletteKey)
			{
				GraphElementPaletteKey other = (GraphElementPaletteKey)obj;
				return
					(this._GraphModeInt == other._GraphModeInt &&
					 this._ClassNumber == other._ClassNumber);
			}
			return false;
		}
		/// <summary>
		/// Typ grafu v aktuálním řádku
		/// </summary>
		public RowGraphMode GraphMode { get { return this._GraphMode; } }
		/// <summary>
		/// Číslo třídy elementu
		/// </summary>
		public int ClassNumber { get { return this._ClassNumber; } }
		private RowGraphMode _GraphMode;
		private int _ClassNumber;
		private int _GraphModeInt { get { return (int)this._GraphMode; } }
	}
	#endregion
	#region STRUCT GraphElementPaletteItem
	/// <summary>
	/// Struktura, která popisuje aktuálně kreslený prvek, včetně jeho barvy.
	/// Tato struktura je klíčem pro položku v paletě. Každý stav elemetu má tedy v paletě svoji oddělenou pozici.
	/// </summary>
	public struct GraphElementPaletteItem
	{
		#region KONSTRUKCE
		public GraphElementPaletteItem(RowGraphMode graphMode, GraphElementShapeType elementShapeType, Color elementColor, GraphElementState elementState)
		{
			_GraphMode = graphMode;
			_ElementShapeType = elementShapeType;
			_ElementColor = elementColor;
			_ElementState = elementState;
		}
		private RowGraphMode _GraphMode;
		private GraphElementShapeType _ElementShapeType;
		private Color _ElementColor;
		private GraphElementState _ElementState;
		#endregion
		#region OVERRIDES ToString(), GetHashCode(), Equals()
		public override string ToString()
		{
			return "{GraphMode=" + GraphMode + "; DrawType=" + ElementShapeType + "; ElementColor=" + ElementColor + "; ElementState=" + ElementState + "}";
		}
		public override int GetHashCode()
		{
			return this._GraphMode.GetHashCode() ^ this._ElementShapeType.GetHashCode() ^ this._ElementColor.GetHashCode() ^ this._ElementState.GetHashCode();
		}
		public override bool Equals(object obj)
		{
			if (obj is GraphElementPaletteItem)
			{
				GraphElementPaletteItem other = (GraphElementPaletteItem)obj;
				return this._GraphMode == other._GraphMode &&
					this._ElementShapeType == other._ElementShapeType &&
					this._ElementColor.Equals(other._ElementColor) &&
					this._ElementState == other._ElementState;
			}
			return false;
		}
		#endregion
		#region PROPERTY
		/// <summary>
		/// Typ grafu v aktuálním řádku
		/// </summary>
		public RowGraphMode GraphMode { get { return this._GraphMode; } }
		/// <summary>
		/// Typ kresleného elementu
		/// </summary>
		public GraphElementShapeType ElementShapeType { get { return this._ElementShapeType; } }
		/// <summary>
		/// Barva elementu
		/// </summary>
		public Color ElementColor { get { return this._ElementColor; } }
		/// <summary>
		/// Stav elementu
		/// </summary>
		public GraphElementState ElementState { get { return this._ElementState; } }
		#endregion
	}
	#endregion
	#region CLASS GraphElementPaletteArgs : DATA PRO VYTVOŘENÍ JEDNOHO NÁSTROJE PALETY
	/// <summary>
	/// GraphElementPaletteArgs : data pro vytvoření jednoho nástroje palety
	/// </summary>
	public class GraphElementPaletteArgs
	{
		#region KONSTRUKCE
		public GraphElementPaletteArgs(GraphElementPaletteItem paletteItem, Rectangle brushArea, GraphicsPaletteBaseCls graphicsPalette)
		{
			_PaletteItem = paletteItem;
			_BrushArea = brushArea;
			_GraphicsPalette = graphicsPalette;
		}
		private GraphElementPaletteItem _PaletteItem;
		private Rectangle _BrushArea;
		private GraphicsPaletteBaseCls _GraphicsPalette;
		#endregion
		#region PROPERTY
		public GraphElementPaletteItem PaletteItem { get { return this._PaletteItem; } }
		public Rectangle BrushArea { get { return this._BrushArea; } }
		/// <summary>
		/// Reference na celou malířskou paletu, kde jsou přítomny podpůrné metody pro generování grafických nástrojů
		/// </summary>
		public GraphicsPaletteBaseCls GraphicsPalette { get { return this._GraphicsPalette; } }
		/// <summary>
		/// Typ grafu v aktuálním řádku (=this.PaletteItem.GraphMode)
		/// </summary>
		public RowGraphMode GraphMode { get { return this.PaletteItem.GraphMode; } }
		/// <summary>
		/// Typ kresleného elementu (=this.PaletteItem.DrawType)
		/// </summary>
		public GraphElementShapeType ElementShapeType { get { return this.PaletteItem.ElementShapeType; } }
		/// <summary>
		/// Barva elementu (=this.PaletteItem.ElementColor)
		/// </summary>
		public Color ElementColor { get { return this.PaletteItem.ElementColor; } }
		/// <summary>
		/// Stav elementu (=this.PaletteItem.ElementState)
		/// </summary>
		public GraphElementState ElementState { get { return this.PaletteItem.ElementState; } }

		#endregion
	}
	#endregion
	#region CLASS GraphicsPaletteBaseCls: MALÍŘSKÁ PALETA, ZÁKLADNÍ TŘÍDA OBSAHUJÍCÍ STATICKÉ METODY
	public class GraphicsPaletteBaseCls
	{
		#region SUBCLASS PaletteItem : jedna položka palety
		/// <summary>
		/// Jedna položka palety = pár pera a štětce pro podobný účel. Nemusí být obojí naplněno.
		/// </summary>
		protected class PaletteItem : IDisposable
		{
			/// <summary>
			/// Pero
			/// </summary>
			public Pen PalettePen;
			/// <summary>
			/// Štětec
			/// </summary>
			public Brush PaletteBrush;
			/// <summary>
			/// Font
			/// </summary>
			public Font PaletteFont;
			/// <summary>
			/// Image
			/// </summary>
			public Image PaletteImage;
			/// <summary>
			/// Druh opakování štětce
			/// </summary>
			public GraphicsPaletteBrushRepeatType BrushRepeatType;
			/// <summary>
			/// Velikost a pozice štětce, pro kterou byl vytvořen. Některé štětce nejsou přenosné na jiné místo!
			/// </summary>
			public Rectangle BrushSize;
			/// <summary>
			/// Konstruktor prázdného objektu
			/// </summary>
			public PaletteItem()
			{
				PalettePen = null;
				PaletteBrush = null;
				PaletteFont = null;
				PaletteImage = null;
				BrushRepeatType = GraphicsPaletteBrushRepeatType.None;
				BrushSize = Rectangle.Empty;
			}
			/// <summary>
			/// Dispose
			/// </summary>
			public void Dispose()
			{
				if (PalettePen != null) PalettePen.Dispose();
				PalettePen = null;
				if (PaletteBrush != null) PaletteBrush.Dispose();
				PaletteBrush = null;
				if (PaletteFont != null) PaletteFont.Dispose();
				PaletteFont = null;
				if (PaletteImage != null) PaletteImage.Dispose();
				PaletteImage = null;
			}
			/// <summary>
			/// Podpora pro Brush závislé na Rectangle: zapamatuj si Rectangle podle aktuálního stavu závislosti
			/// </summary>
			/// <param name="rectangle"></param>
			public void BrushSetCurrentArea(Rectangle rectangle)
			{
				if (this.PaletteBrush == null)
					this.BrushSize = Rectangle.Empty;
				else
					this.BrushSize = _GetBrushRectangle(rectangle);
			}
			/// <summary>
			/// Podpora pro Brush závislé na Rectangle: zjisti, zda aktuální Brush mohu použít pro požadované rozměry.
			/// Vysvětlení:
			/// Některé Brush jsou závislé na pozici Top/Left a na rozměru Width/Height. To je uloženo v this.BrushRepeatType.
			/// Podle této hodnoty porovnám prostor, pro který byl Brush vytvořen, s prostorem pro který má být nyní použit
			/// a určím tak, zda je možno starý Brush použít pro novou plochu.
			/// Vracím true = OK / false = nelze použít (pak je zapotřebí vytvořit nový Brush pro novou plochu).
			/// </summary>
			/// <param name="rectangle"></param>
			/// <returns></returns>
			public bool BrushConvenient(Rectangle rectangle)
			{
				rectangle = _GetBrushRectangle(rectangle);            // Přemístíme rectangle na základní pozici
				return (rectangle.Equals(this.BrushSize));            // Vracíme true, pokud oba prostory jsou shodné
			}
			/// <summary>
			/// Vrátí Rectangle upravený podle aktuálního typu opakování vzoru BrushRepeatType.
			/// Úprava spočívá v přemístění klíčové pozice (Top, Left) na základní pozici (na začátek prvního opakování vzoru).
			/// Nedůležitý směr naplní: počátek = 0, velikost = 1.
			/// </summary>
			/// <param name="rectangle"></param>
			/// <returns></returns>
			private Rectangle _GetBrushRectangle(Rectangle rectangle)
			{
				switch (this.BrushRepeatType)
				{
					case GraphicsPaletteBrushRepeatType.None:
						return Rectangle.Empty;
					case GraphicsPaletteBrushRepeatType.Horizontal:
						return new Rectangle(0, rectangle.Y % rectangle.Height, 1, rectangle.Height);
					case GraphicsPaletteBrushRepeatType.Vertical:
						return new Rectangle(rectangle.X % rectangle.Width, 0, rectangle.Width, 1);
					case GraphicsPaletteBrushRepeatType.Tile:
						return new Rectangle(rectangle.X % rectangle.Width, rectangle.Y % rectangle.Height, rectangle.Width, rectangle.Height);
					case GraphicsPaletteBrushRepeatType.Explicit:
						return rectangle;
				}
				return Rectangle.Empty;
			}
		}
		#endregion
		#region COLOR SHIFT
		/// <summary>
		/// Posune danou barvu o daný posun. Odstín ponechává, posouvá světlost.
		/// </summary>
		/// <param name="color">Vstupní barva</param>
		/// <param name="shift">Posun, zadaný v číslu (+- 255)</param>
		/// <returns>Upravená barva</returns>
		public static Color ColorShift(Color color, int shift)
		{
			int r = _ColorShiftOne(color.R, shift);
			int g = _ColorShiftOne(color.G, shift);
			int b = _ColorShiftOne(color.B, shift);
			return Color.FromArgb(r, g, b);
		}
		/// <summary>
		/// Posune danou barvu o daný posun, v každé složce může být jiný.
		/// </summary>
		/// <param name="color">Vstupní barva</param>
		/// <param name="shift">Posun, zadaný v číslu (+- 255)</param>
		/// <returns>Upravená barva</returns>
		public static Color ColorShift(Color color, int shiftR, int shiftG, int shiftB)
		{
			int r = _ColorShiftOne(color.R, shiftR);
			int g = _ColorShiftOne(color.G, shiftG);
			int b = _ColorShiftOne(color.B, shiftB);
			return Color.FromArgb(r, g, b);
		}
		/// <summary>
		/// Posune jednu barevnou složku o daný posun.
		/// </summary>
		/// <param name="colourComponent"></param>
		/// <param name="shift"></param>
		/// <returns></returns>
		private static int _ColorShiftOne(byte colourComponent, int shift)
		{
			int newColor = colourComponent + shift;
			return ((newColor < 0) ? 0 : ((newColor > 255) ? 255 : newColor));
		}
		#endregion
		#region COLOR BLEND SUPPORT
		/// <summary>
		/// Metoda vygeneruje a vrátí pole relativních pozic konkrétních bodů (např. pro tvorbu ColorBlends) pro zadaný konkrétní rozměr.
		/// Jednotlivé pixely jsou zadávány offsetem: kladné hodnoty jsou ponechány, záporné jsou přičteny k rozměru size.
		/// Platí zásada, že po prvním výskytu záporné hodnoty jsou hodnoty 0 akceptovány rovněž jako záporné = značí to pozici (size == 1.0F).
		/// Poslední hodnotu vždy nastaví na 1.0F. 
		/// Na vstupu musí tento poslední prvek vždy existovat (délka výstupního pole odpovídá délce vstupního pole).
		/// </summary>
		/// <param name="size">Konkrétní velikost plochy = základna</param>
		/// <param name="pixels">Pozice jednotlivých pixelů: kladné jsou chápány beze změny, záporné jsou relativní k End.</param>
		/// <returns>Pole relativních pozic daných pixelů</returns>
		public static float[] GetColorBlendPosition(int size, int[] pixels)
		{
			int length = pixels.Length;
			float[] result = new float[length];
			float sizeF = (float)size;
			float lastF = 0.0F;
			bool onEnd = false;
			for (int i = 0; i < length; i++)
			{
				if (i == 0)
				{
					// První prvek:
					result[i] = 0.0F;
					lastF = result[i];
				}
				if (i == (length - 1))
					// Poslední prvek
					result[i] = 1.0F;
				else
				{	// Prostřední prvky:
					int px = pixels[i];
					if (px < 0 || (px == 0 && onEnd))
					{	// Záporné hodnoty, anebo jsem po prvním výskytu záporné hodnoty pixelu a nyní je hodnota 0:
						px += size;
						// Po prvním výskytu záporné hodnoty pixelu (která je měřena od konce) se nastaví příznak, který chápe i 0 jako mínus:
						onEnd = true;
					}
					float currF = ((float)px / sizeF);
					if (currF < lastF)
						currF = lastF;
					result[i] = currF;
					lastF = currF;
				}
			}
			return result;
		}
		#endregion
	}
	#endregion
	#region ENUM GraphicsPaletteBrushRepeatType : typy opakování obsahu brushe
	/// <summary>
	/// Směry, které jsou klíčové pro opakovatelnost použití Brush.
	/// Některé Brush jsou použitelné na jakoukoli plochu, některé jsou použitelné jen do šířky, jiné do výšky, a některé jsou zcela specifické.
	/// </summary>
	public enum GraphicsPaletteBrushRepeatType
	{
		/// <summary>
		/// Daný brush je použitelný na jakoukoli plochu (standardní jednobarevný štětec, plechovka barvy)
		/// </summary>
		None = 1,
		/// <summary>
		/// Tento Brush obsahuje vodorovný vzor (řádky).
		/// Lze jej opakovaně použít tehdy, když se shoduje Top pozice a Height rozměr 
		/// (přičemž Top pozice je opakovatelná v násobku Height)
		/// </summary>
		Horizontal,
		/// <summary>
		/// Tento Brush obsahuje svislý vzor (slouce).
		/// Lze jej opakovaně použít tehdy, když se shoduje Left pozice a Width rozměr 
		/// (přičemž Left pozice je opakovatelná v násobku Width)
		/// </summary>
		Vertical,
		/// <summary>
		/// Tento Brush obsahuje dlaždicový vzor (obdélníky).
		/// Lze jej opakovaně použít tehdy, když se shoduje Top i Left pozice a Height a Width rozměr 
		/// (přičemž obě jsou opakovatelné v odpovídajícím násobku)
		/// </summary>
		Tile,
		/// <summary>
		/// Tento Brush obsahuje explicitně umístěný vzor, který se neopakuje.
		/// Pro každé nové umístění se generuje nový Brush.
		/// Z paměti se starý Brush opakovaně použije pouze tehdy, když se přesně shoduje původní a nové umístění.
		/// </summary>
		Explicit
	}
	#endregion
}
